package gbench.sandbox.tuple;

import org.junit.jupiter.api.Test;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import gbench.common.tree.LittleTree.IRecord;

import static gbench.common.tree.LittleTree.IRecord.*;

/**
 * 
 * 实现一个模版字符串的替换算法
 * @author gbench
 *
 */
public class JunitTrie {

    @Test
    public void foo() {
        System.out.println(fill_template(
            "这是一条人员信息摘要:\n\t ( name:a.name--sex:a.sex--city:a.city--cityAlias:a.cityAlias )",
            REC( //模版参数
              "a.name", "张三",
              "a.sex", "男",
              "a.city", "上海",
              "a.cityAlias", "沪"
            )// REC
        ));// println
    }
    
    /**
     * 前缀树节点
     * @author gbench
     *
     * @param <T> 节点名称的类型。
     */
    public static class TrieNode<T>{
        
        /**
         * 前缀树的节点
         * @param name 节点名称
         */
        public TrieNode(final T name){
            this.name = name;
        }
        
        /**
         * 节点流
         * @return 节点流
         */
        public Stream<TrieNode<T>> childrenNodes(){
            return this.chidren.entrySet().stream().map(e->e.getValue());
        }
        
        /**
         * 节点
         * @param t 节点的关键词
         * @param parent 父节点
         */
        public TrieNode(final T name, final TrieNode<T> parent){
            this.name = name;
            this.parent = parent;
        }
        
        /**
         * 当前节点是否拥有一个 叫做name的子节点
         * @param name 节点名称。
         * @return 是否有子节点。
         */
        public boolean hasChild(final T name) {
            return this.chidren.containsKey(name);
        }
        
        /**
         * 为当前节点增加一个 叫做name的子节点。如果已经添加 则 直接放回 该name的子节点。
         * @param name 节点名称
         * @return 名称为 name的TrieNode
         */
        public TrieNode<T> addChild(final T name) {
            return chidren.compute(name, (k, v) -> v == null ? new TrieNode<>(k, this) : v);
        }
        
        /**
         * 是否拥有一个单词标记。
         * @return 是否是一个单词的尾部节点。
         */
        public boolean flag() {
            return this.attributes.bool("flag");
        }
        
        /**
         * 添加单词 
         * @param word 单词字符序列
         * @return 添加单词
         */
        @SuppressWarnings("unused")
        public TrieNode<T> addWord(final List<T> word){
            if (false) {// 采用非递归方式实现。好处是 可以效率高。
                var p = this;
                for (var t : word)
                    p = p.addChild(t);
                p.attributes.computeIfAbsent("flag", k -> true);// 设置单词标记
                return p;
            } else { // 采用递归方法来实现,好处是 没有引入本地变量，方便并发
                if (word.size() < 1) {// 标记档次
                    this.attributes.computeIfAbsent("flag", k -> true);// 设置单词标记
                    return this;
                } else {
                    return this.addChild(word.get(0))
                        .addWord(word.subList(1, word.size()));
                }
            }// if
        }
        
        /**
         * 添加单词 
         * @param word 单词字符序列
         * @return 添加单词
         */
        public TrieNode<T> addWord(final T[] word){
            return this.addWord(Arrays.asList(word));
        }
        
        /**
         * 去除根节点之后的字符拼接成一条字符路径（字符序列）
         * @return 字符序列路径。
         */
        public String path() {
            return this.getPath().stream().map(e->e+"").collect(Collectors.joining("")).substring(1);
        }
        
        /**
         * 获取路径
         * @return 添加路径
         */
        public List<T> getPath(){
            if(this.parent==null)
                return Arrays.asList(this.name);
            else {
                final var ll = new LinkedList<>(this.parent.getPath());
                ll.add(this.name);
                return ll;
            }
        }
        
        /**
         * 提取路径中的 trie节点
         * @param tt 路径信息
         * @return trie节点
         */
        public TrieNode<T> getNode(final List<T> tt) {
            if(!tt.get(0).equals(this.name))return null;
            if(tt.size()==1) {
                return  this;
            }else {
                final var tt1 = tt.subList(1, tt.size());
                final var c = this.chidren.get(tt1.get(0));
                return c==null
                ? null
                :c.getNode(tt1);
            }// if
        }
        
        /**
         * 获取给点
         * @param t 字符名称。
         * @return 获取节点
         */
        public TrieNode<T> getNode(final T t) {
            return this.getNode(Arrays.asList(t));
        }
        
        /**
         * 判断 tt 是否构成一个有效前缀。
         * @param tt 单词序列
         * @return 单圈是否前缀
         */
        public boolean isPrefix(final List<T> tt) {
           final var t = this.getNode(tt);
           return t == null?false:true;
        }
        
        /**
         * 判断是否单词序列
         * @param word
         * @return 判断是否单词序列
         */
        public boolean isWord(final List<T> word) {
           final var t = this.getNode(word);
           return t == null?false:t.flag();
        }
        
        /**
         * 获取节点名称
         * @return 节点名称
         */
        public T getName() {
            return this.name;
        }
        
        /**
         * 获取当前节点的阶层号
         * @return 当前的节点的阶层号从1开始。
         */
        public int getLevel() {
            return this.getPath().size();
        }
        
        /**
         * 获取节点名称
         * @return 及诶按属性。
         */
        public IRecord attrs() {
            return this.attributes;
        }
        
        /**
         * 设置节点属性
         * @param name 属性名
         * @param value 属性值
         * @return 节点本身 以实现链式编程
         */
        public TrieNode<T> attrs(final String name, final Object value) {
            this.attributes.set(name, value);
            return this;
        }
        
        /**
         * 获取属性值
         * @param name 属性名
         * @return 属性值
         */
        public Object attr(final String name) {
            return this.attributes.get(name);
        }
        
        /**
         * 获取属性值
         * @param <U> 属性值类型
         * @param name 属性名
         * @return 属性值
         */
        public <U> U attr(final String name, final Class<U> tclass) {
            return this.attributes.get(name,tclass);
        }
        
        /**
         * 遍历访问
         * @param cs 单词回调函数
         */
        public void forEach(final Consumer<TrieNode<T>> cs) {
            cs.accept(this);
            this.chidren.forEach((t,e)->e.forEach(cs));
        }

        /**
         * 字符串格式化
         */
        public String toString() {
            return MessageFormat.format("{0}", this.name);
        }
        
        private T name; // 节点名称
        private TrieNode<T> parent; // 父节点
        private IRecord attributes = REC(); // 节点属性
        private Map<T,TrieNode<T>> chidren = new ConcurrentHashMap<T,TrieNode<T>>();// 子节点 
    }// TrieNode 

    /**
     * 把template中的占位符用rec中的名称给与替换用于做SQL语句的替换
     * @param template 模版字符串
     * @param placeholder2values 关键词列表:占位符以及与之对应的值
     * @return 把template中的占位符用placeholder2values中的值给与替换
     */
    public static String fill_template(final String template, final IRecord placeholder2values) {
        final var len = template.length();// 模版字符串的长度。
        final var buffer = new StringBuffer();// 工作缓存
        final Function<String,List<Character>> lc = strline -> strline.chars().mapToObj(c->(char)c).collect(Collectors.toList());// 把一个字符串转换成一个字符列表
        final var prefixTree = new TrieNode<Character>('/');// 前缀树
        placeholder2values.keys().stream().map(lc).forEach(prefixTree::addWord);// 构建前缀树
        final var stack = new Stack<String>();// 候选集合：即当前业已发现匹配的 候选placeholder集合。 最长的候选placeholder位于栈顶最短的位于栈底。
        int i = 0;// 当前阶段/周期  读取的模版字符位置。即开始位置。
        
        while(i<len) {// 逐字读取
            stack.clear();// 清空候选集合
            int j = i;// 读取位置。从阶段/周期 开始位置 开始。
            var prefixNode = prefixTree.getNode(Arrays.asList('/',template.charAt(i)));// 前缀节点:最开始的前缀
            
            while(prefixNode!=null ) {// 尽力向前读取字符直到不是前缀为止:尝试在从当前位置开始向后读取占位符，尽可能长的读取字符。直到不能够拼成占位付的前缀为止。
                if(prefixNode.flag()) stack.push(prefixNode.path());
                if(j==len) break;
                prefixNode = prefixTree.getNode(lc.apply("/"+template.substring(i, ++j)));
            }//while
            
            if(!stack.empty()) { // 已经发现了占位符号，用placeholder2values中的值给与替换
                boolean withNum = false;
                final var placeholder = stack.pop();// 提取最长的即栈顶元素作为placeholder。
                final var value = placeholder2values.get(placeholder);
                if(placeholder.endsWith("$")) withNum=true;
                if(value instanceof Number) withNum=true;// 如果值是数字类型就一定会不加引号的。
                buffer.append(withNum ? value+"": "'"+value+"'");
                i+=placeholder.length();// 步长移动placeholder长度。
            }else { // 不能匹配占位符， template的子串[i,j)表示模版内容则给予拷贝buffer缓存
                buffer.append(template.substring(i,i==j?j+1:j));// 提取业已读取的(j位置的字符已经读取）字符串长度。
                i = (i==j?j+1:j);// 步进到j的下一位(j位置的字符已经读取）
            }// if placeholder
        }// while
        
        return buffer.toString(); // 返回结果
    }// fill_template
    
    /**
     * 把一个字符串转成 一个Character列表 
     * @param line
     * @return Character列表
     */
    public List<Character> LC(String line) {
        return Arrays.asList((line.split(""))).stream().map(c->Character.valueOf(c.charAt(0))).collect(Collectors.toList());
    }
    
    @Test
    final void bar() {
        final var words = L("good","goodbye","hello","book");
        final var trie = new TrieNode<Character>('/');//  构造一个根节点。
        words.stream().map(this::LC).forEach(trie::addWord);
        trie.forEach(e->{
            final var ident = " ".repeat(e.getLevel());
            if(e.flag()) {// 只显示单词
                System.out.println(ident+e.getName()+" ----> "+e.path());
            }else {
                System.out.println(ident+e.getName());
            }
        });
        
        System.out.println("");
        System.out.println("/good,isPrefix:"+trie.isPrefix(LC("/good")));
        System.out.println("/goodbye,isWorld:"+trie.isWord(LC("/goodbye")));
        System.out.println("");
        System.out.println("/goodby,isPrefix:"+trie.isPrefix(LC("/goodby")));
        System.out.println("/goodby,isWorld:"+trie.isWord(LC("/goodby")));
        
    }

}
